/**
 * 
 */
package org.swing.utility.input.util;

import java.awt.AWTException;
import java.awt.Button;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.awt.LayoutManager;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.TextComponent;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.image.BufferedImage;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolTip;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;

/**
 * @author Le Quynh Nhu
 *
 */
public class GUIUtil {
	/**
	 * Adds a component into a container component of <code> GridBagLayout.
	 *
	 * @param container
	 *            a <code>Container </code> where a component will be added
	 * @param c
	 *            a <code> Component </code> to be added into a container
	 * @param x
	 *            an interger specifying the x coordiante of the added component
	 * @param y
	 *            an integer specifying the y coordinate of the added component
	 * @param w
	 *            an integer specifying the width of the added component
	 * @param h
	 *            an integer specifying the height of the added component
	 * @param anchor
	 *            an interger specifying the anchor point of the added component
	 * @param wx
	 *            an double specifying the extra horizontal space distribution
	 * @param wy
	 *            an double specifying the extra vertical space distribution
	 * @throws AWTException
	 *             Abstract Window Toolkit exception
	 */
	public static void addComponent(Container container, Component c, int x,
			int y, int w, int h, int fill, int anchor, int wx, int wy)
			throws AWTException {
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets = new Insets(5, 5, 2, 2);
		gbc.gridx = x;
		gbc.gridy = y;
		gbc.gridwidth = w;
		gbc.gridheight = h;
		gbc.fill = fill;
		gbc.anchor = anchor;
		gbc.weightx = wx;
		gbc.weighty = wy;
		addComponent(container, c, gbc);
	}

	public static void addComponent(Container container, Component c, int x,
			int y, int w, int h) throws AWTException {
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets = new Insets(5, 5, 2, 2);
		gbc.gridx = x;
		gbc.gridy = y;
		gbc.gridwidth = w;
		gbc.gridheight = h;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.anchor = GridBagConstraints.WEST;
		addComponent(container, c, gbc);
	}

	/** add the component with the label at left to the JPanel at row */
	public static void addInput2Pane(String label, Component c, JPanel p,
			int row) throws AWTException {
		JLabel jlabel = new JLabel(label);
		int a = GridBagConstraints.EAST, f = GridBagConstraints.HORIZONTAL;
		addComponent(p, jlabel, 0, row, 1, 1, f, a, 0, 0);
		addComponent(p, c, 1, row, 1, 1, f, a, 1, 1);
	}

	public static void addComponent(Container container, Component c,
			GridBagConstraints gbc) throws AWTException {
		LayoutManager lm = container.getLayout();
		if (!(lm instanceof GridBagLayout)) {
			throw new AWTException("Invalid layout" + lm);
		} else {
			container.add(c, gbc);
		}
	}

	/**
	 * Gets the toppest parent of a component.
	 *
	 * @param a
	 *            <code> Component </code>
	 * @returns a <code> Frame </code> specifying the toppest parent of a
	 *          component
	 */
	public static Frame getTopestParent(Component component) {
		Component c = component;
		while (c.getParent() != null)
			c = c.getParent();
		if (c instanceof Frame)
			return (Frame) c;
		else
			return null;
	}

	public static final String DARK_LAF_NAME = "Darcula";

	public static Point getRelativeMouseLocation(Component component) {
		PointerInfo pointerInfo = MouseInfo.getPointerInfo();
		if (pointerInfo == null) {
			return new Point();
		} else {
			Point mouseLocation = pointerInfo.getLocation();
			return getRelativeLocation(mouseLocation, component);
		}
	}

	public static Point getRelativeLocation(Point locationOnScreen,
			Component component) {
		Point componentLocation = component.getLocationOnScreen();
		Point relativeLocation = locationOnScreen.getLocation();
		relativeLocation.move(
				(int) (locationOnScreen.getX() - componentLocation.getX()),
				(int) (locationOnScreen.getY() - componentLocation.getY()));
		return relativeLocation;
	}

	public static boolean isChildOf(Component component, Component child) {
		Component parent = child == null ? null : child.getParent();
		while (parent != null) {
			if (parent == component) {
				return true;
			}
			parent = parent.getParent();
		}
		return false;
	}

	public static boolean isFocused(Component component, boolean recoursive) {
		if (component.isFocusOwner())
			return true;
		if (recoursive && component instanceof JComponent) {
			JComponent parentComponent = (JComponent) component;
			for (Component childComponent : parentComponent.getComponents()) {
				if (isFocused(childComponent, recoursive)) {
					return true;
				}
			}
		}
		return false;
	}

	public static boolean isDarkLookAndFeel() {
		return UIManager.getLookAndFeel().getName().contains(DARK_LAF_NAME);
	}

	public static boolean supportsDarkLookAndFeel() {
		if (isDarkLookAndFeel())
			return true;
		for (UIManager.LookAndFeelInfo lookAndFeelInfo : UIManager
				.getInstalledLookAndFeels()) {
			if (lookAndFeelInfo.getName().contains(DARK_LAF_NAME))
				return true;
		}
		return false;
	}

	public static void refresh(Component c) {
		c.invalidate();
		c.validate();
		c.repaint();
		if (c instanceof JComponent) {
			((JComponent) c).revalidate();
		}
	}

	public static Window getWindow(Component com) {
		Component temp = com;

		if (temp == null) {
			return getRootFrame();
		}

		while (!(temp instanceof Frame || temp instanceof Dialog)) {
			temp = temp.getParent();
			if (temp == null) {
				return getRootFrame();
			}
		}

		return (Window) temp;
	}

	public static Frame getRootFrame() {
		return getFrame(null);
	}

	public static Frame getFrame(Component parent) {
		if (parent == null) {
			// So we can't get one by walking up the tree so we will have to
			// get one from somewhere else.
			// Firstly someone might have called JOptionPane.setRootFrame()
			// to give us a reasonable default so try there
			Frame option = JOptionPane.getRootFrame();

			// If a default has not been set, JOptionPane.getRootFrame() calls
			// SwingUtilities.getSharedOwnerFrame() to get a new invisible frame
			// and we may be able to do better than that. Unfortunately the
			// getSharedOwnerFrame() method is not public so we have to trick
			// our way to finding if we got a duff default
			if (!option.getClass().getName()
					.startsWith("javax.swing.SwingUtilities$")) {
				// So we think the JOptionPane root frame is our creation
				return option;
			}

			// We might be able to get a better default by looking through all
			// the frames and picking the biggest visible one.
			Frame best = null;
			int bestSize = 0;

			Frame[] frames = Frame.getFrames();
			for (int i = 0; i < frames.length; i++) {
				Frame frame = frames[i];
				if (frame.isVisible()) {
					// So this frame is a candidate
					int thisSize = frame.getWidth() * frame.getHeight();
					if (best == null || thisSize > bestSize) {
						best = frame;
						bestSize = thisSize;
					}
				}
			}

			// So if we found a frame from searching then use that
			if (best != null) {
				return best;
			}

			// if all else fails we will have to use the invisible frame
			// provided by JOptionPane
			return option;
		}

		if (parent instanceof Frame) {
			return (Frame) parent;
		}

		// So we walk up the tree looking for a frame
		return getFrame(parent.getParent());
	}

	public static void centerOnScreen(Window win) {
		win.setLocationRelativeTo(null);
	}

	public static int setSize(Window win, Dimension requestedDim) {
		// If possible we try to honor the request
		Dimension honoredDim = (Dimension) requestedDim.clone();
		Toolkit tk = win.getToolkit();
		Dimension originalDim = win.getSize();
		Dimension screenDim = tk.getScreenSize();
		boolean isFrame = win instanceof Frame;

		// Frames may allow for maximizing in one direction the other or both
		// store any state changes in requestedState
		int honoredState = 0;

		// If the window is wider than the screen, limit it to the width of the
		// screen
		if (honoredDim.width >= screenDim.width) {
			honoredDim.width = screenDim.width;
		}

		if (honoredDim.height >= screenDim.height) {
			honoredDim.height = screenDim.height;
		}

		if (isFrame && honoredDim.equals(screenDim)
				&& tk.isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
			honoredState |= Frame.MAXIMIZED_BOTH;
		}

		// If either the height or the width changed then use it.
		if (!honoredDim.equals(originalDim)) {
			win.setSize(honoredDim);
		}

		// One of the dimensions may have changed via setSize,
		// and the other may be waiting to change here
		if (honoredState != 0 && win instanceof Frame) {
			Frame frame = (Frame) win;
			// Make sure to preserve existing states
			frame.setExtendedState(honoredState | frame.getExtendedState());
		}

		// setExtendedState can change it.
		Dimension finalDim = win.getSize();
		honoredDim.width = finalDim.width;
		honoredDim.height = finalDim.height;

		return honoredState;
	}

	public static void setSize(Window win, float percentOfScreen) {
		Toolkit tk = win.getToolkit();
		Dimension screenSize = tk.getScreenSize();

		int width = Float.valueOf(screenSize.width * percentOfScreen)
				.intValue();
		int height = Float.valueOf(screenSize.height * percentOfScreen)
				.intValue();
		Dimension winSize = new Dimension(width, height);

		win.setSize(winSize);
	}

	public static void defaultDesktopSize(Window win) {
		float defaultPercent = 0.75F;
		setSize(win, defaultPercent);
	}

	public static void maximizeWindow(Window win) {
		Toolkit tk = Toolkit.getDefaultToolkit();
		// Check to see if the window supports maximizing
		if (win instanceof Frame
				&& tk.isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
			Frame frame = (Frame) win;
			// Make sure to preserve existing states
			frame.setExtendedState(Frame.MAXIMIZED_BOTH
					| frame.getExtendedState());
		} else {
			// No, then just simulate it.
			Dimension screenDim = tk.getScreenSize();
			win.setLocation(0, 0);
			win.setSize(screenDim);
		}
	}

	public static void restrainedRePack(Window win) {
		Dimension orig = win.getSize();
		Dimension max = new Dimension((int) (orig.width * 1.1),
				(int) (orig.height * 1.1));
		Dimension min = new Dimension((int) (orig.width / 1.1),
				(int) (orig.height / 1.1));

		win.pack();

		// If the window is wider than 110% of its original size, clip it
		if (win.getSize().width > max.width) {
			win.setSize(max.width, win.getSize().height);
		}

		// If the window is taller than 110% of its original size, clip it
		if (win.getSize().height > max.height) {
			win.setSize(win.getSize().width, max.height);
		}

		// If the window is narrower than 90% of its original size, grow it
		if (win.getSize().width < min.width) {
			win.setSize(min.width, win.getSize().height);
		}

		// If the window is shorter than 90% of its original size, grow it
		if (win.getSize().height < min.height) {
			win.setSize(win.getSize().width, min.height);
		}

		Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();

		// If the window is wider than the screen, clip it
		if (screenDim.width < win.getSize().width) {
			win.setSize(screenDim.width, win.getSize().height);
		}

		// If the window is taller than the screen, clip it
		if (screenDim.height < win.getSize().height) {
			win.setSize(win.getSize().width, screenDim.height);
		}

		refresh(win);

		// log.log(Level.INFO, "Failure", ex);
		// log.fine("Size was "+orig);
		// log.fine("Size is "+win.getSize());
	}

	public static void restrainedPack(Window win, float maxx, float maxy) {
		win.pack();

		Dimension screen_dim = Toolkit.getDefaultToolkit().getScreenSize();

		// If the window is wider than the screen, clip it
		if (win.getSize().width > (screen_dim.width * maxx)) {
			win.setSize((int) (screen_dim.width * maxx), win.getSize().height);
		}

		// If the window is taller than the screen, clip it
		if (win.getSize().height > (screen_dim.height * maxy)) {
			win.setSize(win.getSize().width, (int) (screen_dim.height * maxy));
		}

		refresh(win);

		// log.log(Level.INFO, "Failure", ex);
		// log.fine("Size was "+orig);
		// log.fine("Size is "+win.getSize());
	}

	public static void enforceMinimumSize(Component comp, int min_width,
			int min_height) {
		if (comp.getSize().width < min_width) {
			comp.setSize(min_width, comp.getSize().height);
		}

		if (comp.getSize().height < min_height) {
			comp.setSize(comp.getSize().width, min_height);
		}
	}

	public static String getText(Component comp) {
		if (comp instanceof JTextComponent) {
			return ((JTextComponent) comp).getText();
		}

		if (comp instanceof JLabel) {
			return ((JLabel) comp).getText();
		}

		if (comp instanceof AbstractButton) {
			return ((AbstractButton) comp).getText();
		}

		if (comp instanceof JComboBox) {
			return ((JComboBox) comp).getSelectedItem().toString();
		}

		if (comp instanceof JToolTip) {
			return ((JToolTip) comp).getTipText();
		}

		if (comp instanceof TextComponent) {
			return ((TextComponent) comp).getText();
		}

		if (comp instanceof Button) {
			return ((Button) comp).getLabel();
		}

		if (comp instanceof Label) {
			return ((Label) comp).getText();
		}

		if (comp instanceof JScrollPane) {
			JScrollPane scr = (JScrollPane) comp;
			Component sub = scr.getViewport().getView();
			if (sub != null) {
				return getText(sub);
			}
		}

		return comp.toString();
	}

	public static void applyOrientation(Component comp,
			ComponentOrientation orientation) {
		comp.setComponentOrientation(orientation);

		if (comp instanceof JMenu) {
			JMenu menu = (JMenu) comp;
			int ncomponents = menu.getMenuComponentCount();
			for (int i = 0; i < ncomponents; ++i) {
				applyOrientation(menu.getMenuComponent(i), orientation);
			}
		} else if (comp instanceof Container) {
			Container container = (Container) comp;
			int ncomponents = container.getComponentCount();
			for (int i = 0; i < ncomponents; ++i) {
				applyOrientation(container.getComponent(i), orientation);
			}
		}
	}

	public static JButton flatten(JButton button) {
		button.setBorderPainted(false);
		button.setContentAreaFilled(false);
		button.setText(null);
		button.setMargin(new Insets(0, 0, 0, 0));
		return button;
	}

	public static void hideCursor(Component frame) {
		// disable mouse cursor in fullscreen mode
		BufferedImage cursorImg = new BufferedImage(16, 16,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D gfx = cursorImg.createGraphics();
		gfx.setColor(new Color(0, 0, 0, 0));
		gfx.fillRect(0, 0, 16, 16);
		gfx.dispose();
		frame.setCursor(frame.getToolkit().createCustomCursor(cursorImg,
				new Point(), ""));
	}

	public static void showCursor(Component frame) {
		frame.setCursor(Cursor.getDefaultCursor());
	}

}
